package com.zm.xiaohashu.user.biz.service.impl;


import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.PhoneUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.zm.exception.BizException;
import com.zm.response.Response;
import com.zm.utils.ParamUtils;
import com.zm.xiaohashu.biz.context.utils.LoginUserContextHolder;
import com.zm.xiaohashu.user.biz.config.PasswordEncoderConfig;
import com.zm.xiaohashu.user.biz.constant.RedisKeyConstants;
import com.zm.xiaohashu.user.biz.constant.RoleConstants;
import com.zm.xiaohashu.user.biz.enums.*;
import com.zm.xiaohashu.user.biz.mapper.UserRoleRelMapper;
import com.zm.xiaohashu.user.biz.model.dto.User;
import com.zm.xiaohashu.user.biz.mapper.UserMapper;
import com.zm.xiaohashu.user.biz.model.dto.UserRoleRel;
import com.zm.xiaohashu.user.biz.model.vo.*;
import com.zm.xiaohashu.user.biz.rpc.DistributedIdGeneratorRpcService;
import com.zm.xiaohashu.user.biz.rpc.OssRpcService;
import com.zm.xiaohashu.user.biz.service.UserService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.checkerframework.checker.units.qual.K;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SessionCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.web.multipart.MultipartFile;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static com.zm.xiaohashu.user.biz.constant.RoleConstants.COMMON_USER_ROLE_KEY;

/**
 * @author 24690
 * @description 针对表【t_user(用户表)】的数据库操作Service实现
 * @createDate 2025-08-13 11:46:58
 */
@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
    @Resource
    private UserMapper userMapper;
    @Resource
    private RedisTemplate<String, String> redisTemplate;
    @Resource
    private UserRoleRelMapper userRoleRelMapper;
    @Resource
    private TransactionTemplate transactionTemplate;
    @Resource(name = "taskExecutor")
    private ThreadPoolTaskExecutor threadPoolTaskExecutor;
    @Resource
    private PasswordEncoder encoder;
    @Resource
    private OssRpcService ossRpcService;
    @Resource
    private DistributedIdGeneratorRpcService idGeneratorRpcService;
    /**
     * 用户信息本地缓存
     */
    private static final Cache<Long, FindUserByIdRspDTO> LOCAL_CACHE = Caffeine.newBuilder()
            .initialCapacity(10000) // 设置初始容量为 10000 个条目
            .maximumSize(10000) // 设置缓存的最大容量为 10000 个条目
            .expireAfterWrite(1, TimeUnit.HOURS) // 设置缓存条目在写入后 1 小时过期
            .build();

    /**
     * 登录和注册
     *
     * @param userLoginReqVO
     * @return
     */
    @Override
    public Response<String> loginAndRegister(UserLoginReqVO userLoginReqVO) {
        String phone = userLoginReqVO.getPhone();
        boolean isPhone = PhoneUtil.isPhone(phone);
        //手机号码格式校验
        Preconditions.checkArgument(isPhone, ResponseCodeEnum.PHONE_NOT_VALID.getErrorMessage());
        Integer type = userLoginReqVO.getType();
        LoginTypeEnum loginTypeEnum = LoginTypeEnum.valueOf(type);
        Long userId = null;
        switch (loginTypeEnum) {
            // 密码
            case PASSWORD -> {
                String password = userLoginReqVO.getPassword();
                Preconditions.checkArgument(StrUtil.isNotBlank(password), ResponseCodeEnum.PASSWORD_NOT_NULL.getErrorMessage());
                LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();
                lqw.eq(User::getPhone, phone);
                User user = getOne(lqw);
                Preconditions.checkArgument(Objects.nonNull(user), ResponseCodeEnum.USER_NOT_EXIST.getErrorMessage());
                boolean isMatch = encoder.matches(password, user.getPassword());
                Preconditions.checkArgument(isMatch, ResponseCodeEnum.PHONE_OR_PASSWORD_ERROR.getErrorMessage());
                userId = user.getId();
            }
            // 验证码
            case VERIFICATION_CODE -> {
                String code = userLoginReqVO.getCode();//用户输入的验证码
                Preconditions.checkArgument(StrUtil.isNotBlank(code), ResponseCodeEnum.VERIFICATION_CODE_NOT_NULL.getErrorMessage());
                String key = RedisKeyConstants.buildVerificationCodeKey(phone);
                //redis中缓存的验证码
                String cacheCode = (String) redisTemplate.opsForValue().get(key);
                /*校验验证码*/
                Preconditions.checkArgument(StrUtil.equals(code, cacheCode), ResponseCodeEnum.VERIFICATION_CODE_ERROR.getErrorMessage());
                //需要判断该用户是否存在用户记录
                LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();
                lqw.eq(User::getPhone, phone);
                User user = getOne(lqw);
                if (Objects.isNull(user)) {
                    userId = registerUser(phone);
                } else {
                    userId = user.getId();
                }
                redisTemplate.delete(key);//删除验证码
            }
        }

        //登录
        StpUtil.login(userId);
        String token = StpUtil.getTokenInfo().getTokenValue();
        return Response.success(token);
    }

    // 注册逻辑
    public Long registerUser(String phone) {
        return transactionTemplate.execute(status -> {
            try {
                //全局唯一id
//                Long xiaohashuId = redisTemplate.opsForValue().increment(RedisKeyConstants.XIAOHASHU_ID_GENERATOR_KEY);
                //注册
                String xiaohashuId = idGeneratorRpcService.getXiaohashuId();
                Long userId = Long.parseLong(idGeneratorRpcService.getUserId());
                User userDO = User.builder()
                        .id(userId)
                        .phone(phone)
                        .xiaohashuId(xiaohashuId) // 自动生成小红书号 ID
                        .nickname("小红薯" + xiaohashuId) // 自动生成昵称, 如：小红薯10000
                        .status(StatusEnum.ENABLE.getValue()) // 状态为启用
                        .createTime(LocalDateTime.now())
                        .updateTime(LocalDateTime.now())
                        .isDeleted(DeletedEnum.NO.getValue()) // 逻辑删除
                        .build();
                save(userDO);//保存用户记录
//                Long userId = userDO.getId();
                // 给该用户分配一个默认角色
                UserRoleRel userRoleDO = UserRoleRel.builder()
                        .userId(userId)
                        .roleId(RoleConstants.COMMON_USER_ROLE_ID)
                        .createTime(LocalDateTime.now())
                        .updateTime(LocalDateTime.now())
                        .isDeleted(DeletedEnum.NO.getValue())
                        .build();
                userRoleRelMapper.insert(userRoleDO);
                /*将用户的role放入redis*/
                // 将该用户的角色 ID 存入 Redis 中
                List<String> roles = Lists.newArrayList();
                roles.add(COMMON_USER_ROLE_KEY);
                String userRolesKey = RedisKeyConstants.buildUserRoleKey(userId);
                redisTemplate.opsForValue().set(userRolesKey, JSONUtil.toJsonStr(roles));
                return userId;
            } catch (Exception e) {
                status.setRollbackOnly();
                return null;
            }
        });
    }

    @Override
    public Response<?> logout() {
        Long userId = LoginUserContextHolder.getUserId();
        StpUtil.logout(userId);
        return Response.success();
    }

    /**
     * 修改密码
     *
     * @param updatePasswordReqVO
     * @return
     */
    @Override
    public Response<?> updatePassword(UpdatePasswordReqVO updatePasswordReqVO) {
        String newPassword = updatePasswordReqVO.getNewPassword();
        Long userId = LoginUserContextHolder.getUserId();
        String encode = encoder.encode(newPassword);
        User user = User.builder()
                .id(userId)
                .password(encode)
                .build();
        boolean isUpdate = updateById(user);
        Preconditions.checkArgument(isUpdate, ResponseCodeEnum.UPDATE_PASSWORD_FAIL.getErrorMessage());
        return Response.success();
    }

    /**
     * 更新用户信息
     *
     * @param updateUserInfoReqVO
     * @return
     */
    @Override
    public Response<?> updateUserInfo(UpdateUserInfoReqVO updateUserInfoReqVO) {
        User userDO = new User();
        // 设置当前需要更新的用户 ID
        userDO.setId(LoginUserContextHolder.getUserId());
        // 标识位：是否需要更新
        boolean needUpdate = false;

        // 头像
        MultipartFile avatarFile = updateUserInfoReqVO.getAvatar();

        if (Objects.nonNull(avatarFile)) {
            String avatarUrl = ossRpcService.uploadFile(avatarFile);
            userDO.setAvatar(avatarUrl);
            needUpdate = true;
        }

        // 昵称
        String nickname = updateUserInfoReqVO.getNickname();
        if (StrUtil.isNotBlank(nickname)) {
            Preconditions.checkArgument(ParamUtils.checkNickname(nickname), ResponseCodeEnum.NICK_NAME_VALID_FAIL.getErrorMessage());
            userDO.setNickname(nickname);
            needUpdate = true;
        }

        // 小哈书号
        String xiaohashuId = updateUserInfoReqVO.getXiaohashuId();
        if (StrUtil.isNotBlank(xiaohashuId)) {
            Preconditions.checkArgument(ParamUtils.checkXiaohashuId(xiaohashuId), ResponseCodeEnum.XIAOHASHU_ID_VALID_FAIL.getErrorMessage());
            userDO.setXiaohashuId(xiaohashuId);
            needUpdate = true;
        }

        // 性别
        Integer sex = updateUserInfoReqVO.getSex();
        if (Objects.nonNull(sex)) {
            Preconditions.checkArgument(SexEnum.isValid(sex), ResponseCodeEnum.SEX_VALID_FAIL.getErrorMessage());
            userDO.setSex(sex);
            needUpdate = true;
        }

        // 生日
        LocalDate birthday = updateUserInfoReqVO.getBirthday();
        if (Objects.nonNull(birthday)) {
            userDO.setBirthday(birthday);
            needUpdate = true;
        }

        // 个人简介
        String introduction = updateUserInfoReqVO.getIntroduction();
        if (StrUtil.isNotBlank(introduction)) {
            Preconditions.checkArgument(ParamUtils.checkLength(introduction, 100), ResponseCodeEnum.INTRODUCTION_VALID_FAIL.getErrorMessage());
            userDO.setIntroduction(introduction);
            needUpdate = true;
        }

        // 背景图
        MultipartFile backgroundImgFile = updateUserInfoReqVO.getBackgroundImg();
        if (Objects.nonNull(backgroundImgFile)) {
            String backgroundImgUrl = ossRpcService.uploadFile(backgroundImgFile);
            userDO.setBackgroundImg(backgroundImgUrl);
            needUpdate = true;
        }

        if (needUpdate) {
            // 更新用户信息
            userDO.setUpdateTime(LocalDateTime.now());
            userMapper.updateById(userDO);
        }
        return Response.success();
    }

    @Override
    public Response<FindUserByIdRspDTO> findById(FindUserByIdReqDTO findUserByIdReqDTO) {
        Long userId = findUserByIdReqDTO.getId();
        // 先从本地缓存中查询
        FindUserByIdRspDTO findUserByIdRspDTOLocalCache = LOCAL_CACHE.getIfPresent(userId);
        if (Objects.nonNull(findUserByIdRspDTOLocalCache)) {
            log.info("==> 命中了本地缓存；{}", findUserByIdRspDTOLocalCache);
            return Response.success(findUserByIdRspDTOLocalCache);
        }
        String userInfoKey = RedisKeyConstants.buildUserInfoKey(userId);
        String userInfoJson = (String) redisTemplate.opsForValue().get(userInfoKey);
        if (StrUtil.isNotBlank(userInfoJson)) {
            log.info("从redis中获取用户信息");
            FindUserByIdRspDTO findUserByIdRspDTO = JSONUtil.toBean(userInfoJson, FindUserByIdRspDTO.class);
            //异步构建本地缓存
            threadPoolTaskExecutor.submit(() -> {
                LOCAL_CACHE.put(userId, findUserByIdRspDTO);
            });
            return Response.success(findUserByIdRspDTO);
        }
        /**
         * 我们在第一步判断了json查到了值并且不是空值就直接返回
         * 这里判断是否为null 是因为如果上一步查到了值那么就是有数据的，程序走到这里，如果这个值存在却不是为null那么这里就是空值
         */
        if (Objects.nonNull(userInfoJson)) {
            log.info("缓存穿透");
            throw new BizException(ResponseCodeEnum.USER_NOT_EXIST);
        }
        //查数据库
        User user = getById(userId);
        log.info("从数据库中获取用户信息");
        if (Objects.isNull(user)) {
            //防止缓存穿透 -->缓存空值过期时间保底30s
            redisTemplate.opsForValue().set(userInfoKey, "", 30, TimeUnit.SECONDS);
            return Response.fail(ResponseCodeEnum.USER_NOT_EXIST);
        }
        //构建返回值
        FindUserByIdRspDTO findUserByIdRspDTO = FindUserByIdRspDTO.builder()
                .id(user.getId())
                .nickName(user.getNickname())
                .avatar(user.getAvatar())
                .build();
        //异步构建缓存
        threadPoolTaskExecutor.execute(() -> {
            //过期时间保底一天+随机秒数 防止缓存雪崩
            long expireSeconds = 60 * 60 * 24 + RandomUtil.randomInt(60 * 60 * 24);
            redisTemplate.opsForValue().set(userInfoKey, JSONUtil.toJsonStr(findUserByIdRspDTO), expireSeconds, TimeUnit.SECONDS);
        });
        return Response.success(findUserByIdRspDTO);
    }

    @Override
    public Response<List<FindUserByIdRspDTO>> findByIds(FindUsersByIdsReqDTO findUsersByIdsReqDTO) {
        ArrayList<FindUserByIdRspDTO> vos = new ArrayList<>();
        List<Long> ids = findUsersByIdsReqDTO.getIds();
        List<String> userInfoKeys = ids.stream().map(id -> RedisKeyConstants.buildUserInfoKey(id)).toList();
        List<String> userInfoJsonsAll = redisTemplate.opsForValue().multiGet(userInfoKeys);
        if (CollUtil.isEmpty(userInfoJsonsAll)) {
            return Response.success(List.of());
        }
        //1.redis中存在的用户信息
        List<FindUserByIdRspDTO> userInfos = userInfoJsonsAll.stream().filter(Objects::nonNull).map(v -> JSONUtil.toBean(v, FindUserByIdRspDTO.class)).toList();
        //redis中存在了所有要查询的用户信息
        if (userInfos.size() == ids.size()) {
            return Response.success(userInfos);
        }
        //2.都不存在
        if (userInfos.size() == 0) {
            LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();
            lqw.in(User::getId, ids);
            List<User> users = list(lqw);
            if (CollUtil.isEmpty(users)) {
                return Response.success(List.of());
            }
            List<FindUserByIdRspDTO> findUserByIdRspDTOS = convertToFindUserByIdRspDTO(users);
            asyncInitRedisCollectZset(findUserByIdRspDTOS, ids);
            return Response.success(findUserByIdRspDTOS);
        }
        //3.部分存在
        List<Long> existIds = userInfos.stream().map(userInfo -> userInfo.getId()).toList();
        List<Long> needQueryIds = ids.stream().filter(id -> !existIds.contains(id)).toList();
        List<User> needQueryUsers = listByIds(needQueryIds);
        if (CollUtil.isEmpty(needQueryUsers)) {
            return Response.success(List.of());
        }
        List<FindUserByIdRspDTO> needQueryUserDTOLists = convertToFindUserByIdRspDTO(needQueryUsers);
        vos.addAll(needQueryUserDTOLists);
        vos.addAll(userInfos);
        //异步批量同步数据到redis
        asyncInitRedisCollectZset(vos, needQueryIds);
        return Response.success(vos);
    }

    //异步pipeline 批量初始化redis数据
    private void asyncInitRedisCollectZset(List<FindUserByIdRspDTO> userList, List<Long> ids) {
        threadPoolTaskExecutor.execute(() -> {
            Map<Long, FindUserByIdRspDTO> map = CollUtil.toMap(userList, new HashMap<>(), FindUserByIdRspDTO::getId);
            redisTemplate.executePipelined(new SessionCallback<>() {
                @Override
                public Object execute(RedisOperations operations) throws DataAccessException {
                    for (Long userId : ids) {
                        FindUserByIdRspDTO findUserByIdRspDTO = map.get(userId);
                        // 用户信息缓存 Redis Key
                        String userInfoRedisKey = RedisKeyConstants.buildUserInfoKey(userId);
                        String value = JSONUtil.toJsonStr(findUserByIdRspDTO);
                        // 过期时间（保底1天 + 随机秒数，将缓存过期时间打散，防止同一时间大量缓存失效，导致数据库压力太大）
                        long expireSeconds = 60 * 60 * 24 + RandomUtil.randomInt(60 * 60 * 24);
                        operations.opsForValue().set(userInfoRedisKey, value, expireSeconds, TimeUnit.SECONDS);
                    }
                    return null;
                }
            });
        });

    }

    private List<FindUserByIdRspDTO> convertToFindUserByIdRspDTO(List<User> users) {
        if (CollUtil.isEmpty(users)) {
            return new ArrayList<>();
        }
        List<FindUserByIdRspDTO> findUserByIdRspDTOS = users.stream().map(user -> {
            FindUserByIdRspDTO findUserByIdRspDTO = FindUserByIdRspDTO.builder()
                    .id(user.getId())
                    .nickName(user.getNickname())
                    .avatar(user.getAvatar())
                    .introduction(user.getIntroduction())
                    .build();
            return findUserByIdRspDTO;
        }).collect(Collectors.toList());
        return findUserByIdRspDTOS;
    }


}




